/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package auctions;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Random;
import java.util.Vector;

/**
 *
 * @author zxyang
 */
public class SE2PSB_ScheduleAgent implements ScheduleAgent {

    private int agentIdx;
    private int bidderNum;
    private int slotNum;
    private int ddl;
    private int needs;
    private int currentAuctionIdx;
    private double[] utilities;
    private BidderResult myResult;
    private boolean hasPricePrediction;
    private double[] pricePrediction;
    private double[][] priceDistribution;
    private double[] bids;
    private Vector<AuctionResult> a;
    private SE2PSB_strategy strategy;
    private HashMap<SE2PSB_strategy, double[]> _bidsAllStrategies;
    private int win_num;

    public enum SE2PSB_strategy {

        BASELINE, ExpectedMU
    }

    public SE2PSB_ScheduleAgent(int idx, int bidders, int slots,
            int needs, int ddl, double[] utils) {

        this.agentIdx = idx;
        this.bidderNum = bidders;
        this.slotNum = slots;
        this.currentAuctionIdx = 0;
        this.myResult = new BidderResult(idx, slots);
        this.bids = new double[slots];
        this.hasPricePrediction = false;

        this.resetValue(needs, ddl, utils);
        Arrays.fill(this.bids, -1);
        this.setStrategy(SE2PSB_strategy.BASELINE);
        this._bidsAllStrategies = new HashMap<SE2PSB_strategy, double[]>();
        for (SE2PSB_strategy s : SE2PSB_strategy.values()) {
    		_bidsAllStrategies.put(s, new double[slots]);
    	}
        this.a = new Vector<AuctionResult>();
        win_num=0;
    }

        public SE2PSB_ScheduleAgent (SE2PSB_ScheduleAgent toCopy) {
    	this.agentIdx = toCopy.agentIdx;
        this.bidderNum = toCopy.bidderNum;
        this.slotNum = toCopy.slotNum;
        this.currentAuctionIdx = 0;
        this.myResult = new BidderResult(toCopy.agentIdx, toCopy.slotNum);
        this.bids = new double[toCopy.slotNum];
        this.hasPricePrediction = toCopy.hasPricePrediction;

        this.resetValue(toCopy.needs, toCopy.ddl, toCopy.utilities);
        Arrays.fill(this.bids, -1);
        this.setStrategy(toCopy.strategy);
        this._bidsAllStrategies = new HashMap<SE2PSB_strategy, double[]>();
        for (SE2PSB_strategy s : SE2PSB_strategy.values()) {
    		_bidsAllStrategies.put(s, new double[toCopy.slotNum]);
    	}
        this.a = new Vector<AuctionResult>();
        win_num=0;
//        System.out.print("Copy of Agent #" + Integer.toString(this.agentIdx) + " Needs:" + Integer.toString(this.needs) + " Util: ");
//    			for (int n=0;n<this.utilities.length;n++) {
//  				System.out.print(Double.toString(this.utilities[n])+", ");
//   			}
//    			System.out.print("\n");
    }

    public double getBid() {

        /* if current time is already later than ddl */
        if (this.currentAuctionIdx > this.ddl) {
            return -1;
        }

        if(this.needs==0){
            return -1;
        }

        double curr_bid;

        int expectedCompletionTime = this.needs-win_num + currentAuctionIdx-1;

        SE2PSB_strategy s = this.strategy;
        switch (s) {
            case BASELINE:
                /* 
                 * Baseline strategy without prediction 
                 */ 
                if (expectedCompletionTime>=this.slotNum || needs-win_num == 0) this._bidsAllStrategies.get(s)[this.currentAuctionIdx] =  0;
				else this._bidsAllStrategies.get(s)[this.currentAuctionIdx] =(this.utilities[expectedCompletionTime]-myResult.getCost())/(needs-win_num);
                
                //this._bidsAllStrategies.get(s)[this.currentAuctionIdx] = this.bids[0];
               // return this.bids[0];
             break;
//            case StraightMV:
//                /*
//                 * StraightMV
//                 */
//                //if (!this.hasPricePrediction) {
//                //    this.predictPrice(this.strategy);
//               // }
//                //curr_bid = this.marginalValueOf(this.pricePrediction);
//                curr_bid = this.marginalValueOf(GlobalParameters.PointPricePrediction_SE2PSB_StraighMV);
//                double bids_sum = 0;
//                //if(this.currentAuctionIdx>0){
//
//                  //  ArrayList<Double> lastbids = a.get(this.currentAuctionIdx-1).getBidsOfRound(0);
//
//                  //  for(int i=0; i<lastbids.size();i++){
//                   //     bids_sum = bids_sum+lastbids.get(i);
//                  //  }
//                  //  if((bids_sum>40&&currentAuctionIdx==1)) { curr_bid = bids_sum*curr_bid/(210);
//                   // double coef = bids_sum/(210);
//                    //System.out.println("coef: "+coef);}
//               // }
//                //curr_bid = bids_sum*curr_bid/(140);
//                this._bidsAllStrategies.get(s)[this.currentAuctionIdx] = curr_bid;
//               // return curr_bid;
//            break;
//            case StraightMU:
//                /*
//                 * StrightMU using price distribution
//                 */
//               // if (!this.hasPricePrediction) {
//                //    this.predictPriceDist(this.strategy);
//               // }
//
//                this.pricePrediction = new double[this.slotNum];
//                  for(int i=0;i<this.slotNum;i++) {
//                     for(int j=0;j<=GlobalParameters.MaxPrice;j++)
//                this.pricePrediction[i]+=(GlobalParameters.PointPricePrediction_SE2PSB_StraightMU[i][j]*j);
//        }
//                //curr_bid = this.marginalValueOf(this.pricePrediction);
//                curr_bid = this.marginalValueOf(this.pricePrediction);
//
//                this._bidsAllStrategies.get(s)[this.currentAuctionIdx] = curr_bid;
//               // return curr_bid;
//                break;
//            /* TODO SE2PSB: more strategies */
//            case WEIGHTED:
//				if (expectedCompletionTime>=this.slotNum || needs-win_num == 0) this._bidsAllStrategies.get(s)[this.currentAuctionIdx] =  0;
//				else {
//					//Compute the weighted coefficient
//					double coef = 0;
//					for (int i = currentAuctionIdx; i<=expectedCompletionTime; i++) {
//						//coef+=this.utilities[i];
//						//coef+=i-currentAuctionIdx+1;
//
//						coef+=Math.pow(2,(i-currentAuctionIdx));
//					}
//
//					//coef=this.utilities[currentAuctionIdx]/coef;
//					//coef=((expectedCompletionTime-currentAuctionIdx+1))/coef;
//					coef=Math.pow(2,(expectedCompletionTime-currentAuctionIdx))/coef;
//                    //System.out.println(coef);
//					this._bidsAllStrategies.get(s)[this.currentAuctionIdx] = coef*(this.utilities[expectedCompletionTime]-myResult.getCost())/(needs-win_num);
//				}
//				break;
//            case WEIGHTED_EXPOSURE_PADDING:
//				if (expectedCompletionTime>=this.slotNum || needs-win_num == 0) this._bidsAllStrategies.get(s)[this.currentAuctionIdx] =  0;
//				else {
//					//Compute the weighted coefficient
//					double coef = 0;
//					for (int i = currentAuctionIdx; i<=expectedCompletionTime; i++) {
//						//coef+=this.utilities[i];
//						//coef+=i-currentAuctionIdx+1;
//
//						coef+=Math.pow(2,(i-currentAuctionIdx));
//					}
//
//					//coef=this.utilities[currentAuctionIdx]/coef;
//					//coef=((expectedCompletionTime-currentAuctionIdx+1))/coef;
//					coef=Math.pow(2,(expectedCompletionTime-currentAuctionIdx))/coef;
//                    //System.out.println(coef);
//					this._bidsAllStrategies.get(s)[this.currentAuctionIdx] = coef*(this.utilities[expectedCompletionTime]+myResult.getCost()-1);
//				}
//				break;
            case ExpectedMU:
                System.out.println("Starting ExpectedMU for bid" + Integer.toString(this.currentAuctionIdx) + "...");
                boolean[] available = new boolean[this.slotNum];
                Arrays.fill(available, false);
                for(int avail = this.currentAuctionIdx; avail<this.slotNum; avail++) available[avail] = true;
                this._bidsAllStrategies.get(s)[this.currentAuctionIdx] = this.getMarginalUtility(this.myResult.getItemsWon(), available, this.currentAuctionIdx);
                break;
            default:
                System.err.println("Strategy " + this.strategy.toString() + " not implemented.");
                break;
        }
      


        return this._bidsAllStrategies.get(this.strategy)[this.currentAuctionIdx];
    }


    public double computeRegret() {
		double maxProfit = this.myResult.getProfit();
		for (SE2PSB_strategy s : SE2PSB_strategy.values()) {
			if (s.compareTo(this.strategy) == 0) continue;

			double cost = 0;
			boolean[] won = new boolean[this.slotNum];
			double util = 0;
			Arrays.fill(won, false);
			//First compute the new set of items won
			for (int i = 0; i<this.slotNum; i++) {
                System.out.println("Bidder: " + Integer.toString(this.agentIdx) + " Strategy:" + s.name() + " Item: " + Integer.toString(i) + " Bid: " + Double.toString(this._bidsAllStrategies.get(s)[i]));
                ArrayList<Double> bidHistory = this.a.get(i).getBidsOfRound(0);
				bidHistory.set(agentIdx, this._bidsAllStrategies.get(s)[i]);  //Update my bid

				//Compute new winner of slot
				double maxBid = 0;
		        int maxBidder = -1;
		        int aIdx;
		        Double[] bids = new Double[this.bidderNum];
		        for(aIdx=0; aIdx<this.bidderNum;aIdx++) {
		            bids[aIdx]=bidHistory.get(aIdx);
		            if(bids[aIdx] > maxBid) {
		                maxBid = bids[aIdx];
		                maxBidder = aIdx;
		            }
		        }
		        if (maxBidder != this.agentIdx) continue;  //I did not win so try other auctions
		        /* no valid bid does not deal with new bid below reserve price*/
//		        if(maxBid<this._auctionResults.get(i)) {
//		            this.result.setFinalResult(new AuctionStatus(-1, maxBid, this.reservePrice));
//		            for (ScheduleAgent a : this.agents)
//		                a.reportResult(this.result);
//		            return;
//
//		        } else {
//		            this.deal = true;
//		        }

		        Arrays.sort(bids);

		        //Now determine the winning price:  specific to 2PSB with N=8 agents and no reserve
		        double price;
		        int priceIdx = this.bidderNum - 2;//this.paymentRule;
		        if(priceIdx<0) {
		            price = 0; //this.reservePrice;
		        } else {
		            price = bids[priceIdx];
		            if(price<0)
		                price = 0;
		        }
		        cost+=price;
		        won[i] = true;
			}

			int winCount=0;
			int i;
			for(i=0;i<this.slotNum;i++) {
				if(won[i])
					winCount++;
				if(winCount==this.needs) {
					util = (this.utilities[i]);
					break;
				}
			}
			if(i>=this.slotNum) util = 0;
			if ((util-cost)>maxProfit) maxProfit = util - cost;
		}
		return maxProfit-this.myResult.getProfit();
	}

    public void reportResult(AuctionResult lastAuctionResult) {
        int curAucIdx = lastAuctionResult.getIdx();
        a.add(lastAuctionResult);
        if(lastAuctionResult.getFinalResult().currentWinner==this.agentIdx) {
            win_num++;
            this.myResult.addWinning(curAucIdx,
                    lastAuctionResult.getFinalResult().currentPrice);
            if(win_num == this.needs)
                this.myResult.setUtil(this.utilities[curAucIdx]);
        }

        if(curAucIdx>=this.slotNum-1 && this.needs-win_num>0) { /* last auction */
                this.myResult.setUtil(0);
        }

        this.currentAuctionIdx++;
    }

    public void updateStatus(AuctionStatus lastBidStatus) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public BidderResult getBidderResult() {
        return this.myResult;
    }

    public final void resetValue(int needs, int ddl, double[] utils) {
        this.ddl=ddl;
        this.needs = needs;
        if(utils!=null)
            this.utilities=Arrays.copyOf(utils, this.slotNum);
    }

    public void clear() {
        this.currentAuctionIdx = 0;
        this.myResult.clear();
        Arrays.fill(this.bids, -1);
    }

    public final void setStrategy(SE2PSB_strategy s) {
        this.strategy = s;
    }

    public double marginalValueOf(double[] prices) {
        double[] prices1 = Arrays.copyOf(prices, this.slotNum);
        double[] prices2 = Arrays.copyOf(prices, this.slotNum);
        prices1[this.currentAuctionIdx] = 0;
        prices2[this.currentAuctionIdx] = GlobalParameters.BigInt;
        return this.optimalSurplus(prices1) - this.optimalSurplus(prices2);
    }

    public double optimalSurplus(double[] prices) {
        int i = 0, j = 1;
        while (i < this.slotNum) {
            j *= 2;
            i++;
        }
        boolean[] bits;
        int slot, win_count;
        double surplus, max_surplus = 0;
        for (i = 0; i < j; i++) {
            bits = ConvertType.intToBinaryArr(i, this.slotNum);
            win_count = 0;
            surplus = 0;
            for (slot = this.currentAuctionIdx; slot < this.slotNum; slot++) {
                if (slot > this.ddl) {
                    break;
                }
                if (bits[slot]) {
                    win_count++;
                    surplus -= prices[slot];
                    if (win_count >= this.needs) { /* mission completed */
                        surplus += this.utilities[slot];
                        if (surplus > max_surplus) {
                            max_surplus = surplus;
                        }
                        break;
                    }
                }
            }
        }
        return max_surplus;
    }

    public void predictPrice(SE2PSB_strategy sty){
        System.out.println("[SE2PSB]Bidder #"+Integer.toString(this.agentIdx)
                +" is performing self-confirming price point prediction...");
        this.pricePrediction = new double[this.slotNum];
        Arrays.fill(this.pricePrediction, GlobalParameters.PointPriceInit);

        int N=this.bidderNum;
        int M=this.slotNum;
        Random r=new Random();
        ArrayList<ScheduleAgent> agents = new ArrayList<ScheduleAgent>();
        int i, j, k;
        for(i=0; i<N; i++) {
            SE2PSB_ScheduleAgent agent = new SE2PSB_ScheduleAgent(i, N, M, 0, 0, null);
            agent.setStrategy(sty);
            agents.add(agent);
        }
        ScheduleAuctionhouse house = new ScheduleSequentialAuctionHouse(M, AuctionType.SEALEDBID);
        house.setAgents(agents);

        /*price accumulation in iteration*/
        double[] accum_price = new double[this.slotNum];
        /*decaying sequence*/
        double[] decay = new double[GlobalParameters.MaxIteration];
        for(i=0; i<decay.length; i++)
            decay[i]=1-(double)i/decay.length;

        int lamda;
        double[] utils = new double[M];
        double tmp_diff, tmp_max;
        for (int iter=0; iter<GlobalParameters.MaxIteration; iter++) {
            System.out.print("Iter "+Integer.toString(iter));
            /*TODO reset random seed here?*/
            /*run multiple random instances*/
            Arrays.fill(accum_price, 0); /*reset accumulation*/
            for(i=0; i<GlobalParameters.NumInstances; i++) {
                house.clear();
                for(j=0; j<N; j++) {
                    lamda = r.nextInt(M)+1;
                    for (k=0; k<M; k++)
                        utils[k]=(double)r.nextInt(GlobalParameters.MaxPrice)+1;
                    Arrays.sort(utils);
                    for(k=0;k<M/2;k++){
                        double tmp_d = utils[k];
                        utils[k] = utils[M-1-k];
                        utils[M-1-k]= tmp_d;
                    }
                    agents.get(j).resetValue(lamda, M-1, utils);
                    ((SE2PSB_ScheduleAgent)(agents.get(j))).setPricePrediction(this.pricePrediction);
                }
                house.startAuctions();

                for(j=0;j<this.slotNum;j++)
                    accum_price[j]+=house.getAuctionsResults().get(j).getFinalResult().currentPrice;
            }
            tmp_max=0;
            for(j=0;j<this.slotNum;j++) {
                /*average from instance prices*/
                accum_price[j]/=GlobalParameters.NumInstances;
                /*new price*/
                accum_price[j]=this.pricePrediction[j]
                        +decay[iter]*(accum_price[j]-this.pricePrediction[j]);
                /*calc diff*/
                tmp_diff=Math.abs(accum_price[j]-this.pricePrediction[j]);
                if(tmp_diff>tmp_max)
                    tmp_max=tmp_diff;
                /*set new price*/
                this.pricePrediction[j]=accum_price[j];
            }
            System.out.println(" "+Double.toString(tmp_max));

            if(tmp_max<=GlobalParameters.ConvergeThres)
                break;
        }

        this.hasPricePrediction=true;

        // System.out.println("{");
        for(i=0;i<this.slotNum;i++) {
           // System.out.print("\t{");
           // for(j=0;j<=GlobalParameters.MaxPrice;j++) {
               // if(j!=0) System.out.print(", ");
                System.out.println(Double.toString(this.pricePrediction[i]));


           // System.out.println("}");
        }
       // System.out.print("\n\t}\n");

    //}

    }

     public void setPricePrediction(double[] prices) {
        this.pricePrediction=Arrays.copyOf(prices, this.slotNum);
        this.hasPricePrediction=true;
    }

      public void predictPriceDist(SE2PSB_strategy sty) {
        /* self-confirming prediction */
        System.out.println("Bidder #"+Integer.toString(this.agentIdx)
                +" is performing self-confirming price distribution prediction...");
        int N=this.bidderNum;
        int M=this.slotNum;
        int i,j,k;
        this.priceDistribution = new double[M][GlobalParameters.MaxPrice+1];
        for(i=0; i<M; i++)
            Arrays.fill(this.priceDistribution[i], GlobalParameters.DistPriceInit);

        Random r=new Random();
        ArrayList<ScheduleAgent> agents = new ArrayList<ScheduleAgent>();
        for(i=0; i<N; i++) {
            SE2PSB_ScheduleAgent agent = new SE2PSB_ScheduleAgent(i, N, M, 0, 0, null);
            agent.setStrategy(sty);
            agents.add(agent);
        }
        ScheduleAuctionhouse house = new ScheduleSequentialAuctionHouse(M, AuctionType.SEALEDBID);
        house.setAgents(agents);

        /*distribution in iteration*/
        double[][] accum_dist = new double[M][GlobalParameters.MaxPrice+1];

        /*decaying sequence*/
        double[] decay = new double[GlobalParameters.MaxIteration];
        for(i=0; i<decay.length; i++)
            decay[i]=1-(double)i/decay.length;

        int lamda;
        double[] utils = new double[M];
        double tmp_diff, tmp_max;
        for (int iter=0; iter<GlobalParameters.MaxIteration; iter++) {
            System.out.print("Iter "+Integer.toString(iter));
            /*TODO reset random seed here?*/

            /*
             * run multiple random instances
             */

            /*reset accumulation*/
            for(i=0; i<M; i++)
                Arrays.fill(accum_dist[i], 0);
            for(i=0; i<GlobalParameters.NumInstances; i++) {
                house.clear();
                for(j=0; j<N; j++) {
                    lamda = r.nextInt(M)+1;
                    for (k=0; k<M; k++)
                        utils[k]=(double)r.nextInt(GlobalParameters.MaxPrice)+1;
                    Arrays.sort(utils);
                    for(k=0;k<M/2;k++) {
                        double tmp_d = utils[k];
                        utils[k]=utils[M-1-k];
                        utils[M-1-k]=tmp_d;
                    }
                    agents.get(j).resetValue(lamda, M-1, utils);
                    ((SE2PSB_ScheduleAgent)(agents.get(j))).setPriceDistribution(this.priceDistribution);
                }
                house.startAuctions();

                for(j=0;j<this.slotNum;j++) {
                    int roundedPrice=(int)Math.round(house.getAuctionsResults().get(j).getFinalResult().currentPrice);
                    if(roundedPrice>GlobalParameters.MaxPrice || roundedPrice<0) {
                        System.err.println("Price out of bound.");
                        continue;
                    }
                    accum_dist[j][roundedPrice]+=1.0;
                }
            }
            tmp_max=0;
            for(j=0;j<this.slotNum;j++) {
                for(k=0;k<=GlobalParameters.MaxPrice;k++) {
                    /*normalize instance prices*/
                    accum_dist[j][k]/=GlobalParameters.NumInstances;
                    /*new price*/
                    accum_dist[j][k] = this.priceDistribution[j][k]
                            +decay[iter]*(accum_dist[j][k]-this.priceDistribution[j][k]);
                    /*calc diff*/
                    tmp_diff=Math.abs(accum_dist[j][k]-this.priceDistribution[j][k]);
                    if(tmp_diff>tmp_max)
                        tmp_max=tmp_diff;
                    /*set new price*/
                    this.priceDistribution[j][k]=accum_dist[j][k];
                }
            }
            System.out.println(" "+Double.toString(tmp_max));

            if(tmp_max<=GlobalParameters.ConvergeKSThres)
                break;
        }

        /* also, set point predition as the expectation of distribution */
        this.pricePrediction = new double[this.slotNum];
        for(i=0;i<this.slotNum;i++) {
            for(j=0;j<=GlobalParameters.MaxPrice;j++)
                this.pricePrediction[i]+=(this.priceDistribution[i][j]*j);
        }

        this.hasPricePrediction=true;
        
         //Display distribution
        System.out.println("{");
        for(i=0;i<this.slotNum;i++) {
            System.out.print("\t{");
            for(j=0;j<=GlobalParameters.MaxPrice;j++) {
                if(j!=0) System.out.print(", ");
                System.out.print(Double.toString(this.priceDistribution[i][j]));
                if(j%5==4)
                    System.out.print("\n");
            }

            System.out.println("}");
        }
        System.out.print("\n\t}\n");
         
    }

       public void setPriceDistribution(double[][] dist) {
        this.priceDistribution = dist;

        /* also, set point predition as the expectation of distribution */
        int i,j;
        this.pricePrediction = new double[this.slotNum];
        for(i=0;i<this.slotNum;i++) {
            for(j=0;j<=GlobalParameters.MaxPrice;j++)
                this.pricePrediction[i]+=(this.priceDistribution[i][j]*j);
        }

        this.hasPricePrediction=true;
    }

    private double getMarginalUtility(boolean[] owned, boolean[] available, int itemNum) {
       available[itemNum] = false;
       owned[itemNum] = true;
       double first = this.expectedAcquisitionFunction(owned, available);
       owned[itemNum] = false;
       double second = this.expectedAcquisitionFunction(owned, available);
       return first-second;
   }

   private double expectedAcquisitionFunction(boolean[] owned, boolean[] available) {
       double[] prices = new double[owned.length];
       Arrays.fill(prices, 0);
       int start = 0;
       for (start = 0; start<available.length; start++) if(available[start]) break;
       //Fill price vector
       //System.out.println(Arrays.toString(available));
       for (int i = 0; i<start-1;i++) prices[i] = this.a.get(i).getFinalResult().currentPrice;
       prices[start-1] = 0;
       System.out.println("Starting Recursive Computation of Interal...");
       return this.recursiveExpectedAcqIntegral(owned, available, prices, 1, start, start, owned.length);
   }

   private double recursiveExpectedAcqIntegral(boolean[] owned, boolean[] available, double[] prices, double increment, int start, int itemNumber, int length) {
       /**
        * Base Case: Should return acquisition function times the probability of the price vector times the increment
        * Assumes price distributions are indepentdent
        */
       if(itemNumber>=length) {
           return this.acquisitionFunction(owned, available, prices)*this.probabilityOfPrices(prices, start);
       }

       double sum = 0;
       for(double i=0; i<=50; i+=increment) {
           prices[itemNumber] = i;
           sum+=this.recursiveExpectedAcqIntegral(owned, available, prices, increment, start, itemNumber+1, length);
       }
       //System.out.println("Finished Recursion Level:" + Integer.toString(itemNumber));
       return sum;
   }

   private double probabilityOfPrices(double[] prices, int start) {
       double probability = 1;
       for (int i = start; i<prices.length; i++) {
           int integerPrice = (int) Math.round(prices[i]);
           probability*=GlobalParameters.PriceDistPredict_S2PSB_StraightMU[i][integerPrice];
       }
       return probability;
   }
    private double acquisitionFunction(boolean[] owned, boolean[] available, double[] prices) {
        /**
         * Prices must include the prices from closed auctions.  As auctions close the integral will get smaller
         */
        int start = 0;
        int ownedItems = 0;
        for (start = 0; start<available.length; start++) {
            if(owned[start]) ownedItems++;
            if(available[start]) break;
        }
        if (ownedItems>=needs) return valueOfSet(owned, owned, prices, true);  //In this environment if we own enough slots we cannot increase our value buy buying

        boolean[] indexes = Arrays.copyOf(owned, owned.length);

        return this.recursiveMaxBundle(owned, indexes, prices, start, available.length);
    }

    private double recursiveMaxBundle(boolean[] owned, boolean[] items, double[] prices, int itemNum, int recur) {
        if (itemNum>=recur) return valueOfSet(owned, items, prices, false);

        items[itemNum] = true;
        double valTrue = this.recursiveMaxBundle(owned, items, prices, itemNum+1, recur);
        items[itemNum] = false;
        double valFalse = this.recursiveMaxBundle(owned, items, prices, itemNum+1, recur);
        if (valTrue>valFalse) {
            items[itemNum] = true;
            return valTrue;
        } else {
            items[itemNum] = false;
            return valFalse;
        }
    }

    private double valueOfSet(boolean[] owned, boolean[] goods, double[] prices, boolean countAllPrices) {
        int win_count = 0;
        double price = 0;
        int completionTime=-1;
        for (int i = 0; i<goods.length; i++) {
            if (countAllPrices) {
                if(owned[i]) {
                    price+=prices[i];
                    win_count++;
                }
            } else {
               if(goods[i] && !owned[i]){
                price+=prices[i];
                win_count++;
            } else if (goods[i]) {
                win_count++;
            }
            }
            
            if (win_count == needs) completionTime = i;
        }
        if (completionTime>0) return this.utilities[completionTime]-price;
        else return -price;
    }

}
